package webhook

import (
	"encoding/json"
	"fmt"
	"go.uber.org/zap"
	"io"
	admissionv1 "k8s.io/api/admission/v1" // 通过kubectl api-versions | grep admission获取具体版本
	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"net/http"
	"strings"
)

const (
	// 注解
	annotationValidateKey       = "admission-webhook-demo.com/validate" // 是否需要validate
	annotationMutateKey         = "admission-webhook-demo.com/mutate"   // 是否需要mutate
	annotationStatusKey         = "admission-webhook-demo.com/status"
	annotationSidecarImageKey   = "admission-webhook-demo.com/sidecar-image"
	annotationSidecarNameKey    = "admission-webhook-demo.com/sidecar-name"
	annotationSidecarCommandKey = "admission-webhook-demo.com/sidecar-command"
)

var (
	ignoredNamespaces = []string{
		metav1.NamespaceSystem, // kube-system
	}
)

var (
	runtimeScheme = runtime.NewScheme()                       // 创建一个新的运行时方案，用于注册API资源
	codecs        = serializer.NewCodecFactory(runtimeScheme) // 创建基于该方案的编解码器工厂，用于序列化和反序列化API对象
	deserializer  = codecs.UniversalDeserializer()            // 获取一个通用解串器，用于将请求中的JSON数据反序列化为具体的Kubernetes API对象
)

const (
	funcParams = "func params"
	funcResult = "func result"
)

type WhServer struct {
	Server      *http.Server
	PrivateRepo string
}

// patchOperation JSON补丁
type patchOperation struct {
	Op    string      `json:"op"`
	Path  string      `json:"path"`
	Value interface{} `json:"value,omitempty"`
}

func init() {
	_ = corev1.AddToScheme(runtimeScheme)
	_ = admissionregistrationv1.AddToScheme(runtimeScheme)
}

// admissionRequired 是否需要执行准入控制
// 1.namespace在ignoredNamespace的不进行控制
// 2.注解中指定不校验的不控制
func admissionRequired(admissionAnnotationKey string, metadata *metav1.ObjectMeta) bool {
	zap.L().Debug(
		funcParams,
		zap.String("admissionAnnotationKey", admissionAnnotationKey),
		zap.Any("metadata", metadata),
	)
	// 忽略的ns不需执行准入控制
	for _, namespace := range ignoredNamespaces {
		if metadata.Namespace == namespace {
			zap.L().Info(
				"Ignore namespace, Skip...",
				zap.String("name", metadata.Name),
				zap.String("namespace", metadata.Namespace),
				zap.Any("ignored_namespaces", ignoredNamespaces),
			)
			return false
		}
	}

	annotations := metadata.GetAnnotations()
	if annotations == nil {
		annotations = map[string]string{}
	}

	// 查看注解Key是否需要执行准入控制
	var required bool
	admissionAnnotationValue := strings.ToLower(annotations[admissionAnnotationKey])
	switch admissionAnnotationValue {
	default:
		required = true
	case "n", "no", "false", "off":
		required = false
		zap.L().Info(
			"Ignore annotation, Skip...",
			zap.String("name", metadata.Name),
			zap.String("namespace", metadata.Namespace),
			zap.String("annotation_key", admissionAnnotationKey),
			zap.String("annotation_value", admissionAnnotationValue),
		)
	}
	return required
}

// parseResource 解析资源
func parseResource(raw []byte, obj interface{}) error {
	if err := json.Unmarshal(raw, obj); err != nil {
		zap.L().Error("Unmarshal failed", zap.Error(err), zap.Any("raw", raw))
		return err
	}
	return nil
}

// buildErrorResponse 构造错误AdmissionResponse
func buildErrorResponse(code int32, errMsg string) *admissionv1.AdmissionResponse {
	return &admissionv1.AdmissionResponse{
		Allowed: false,
		Result: &metav1.Status{
			Code:    code,
			Status:  "Failure",
			Message: errMsg,
		},
	}
}

// Serve 入口函数
func (whsvr *WhServer) Serve(w http.ResponseWriter, r *http.Request) {
	var body []byte
	if r.Body != nil {
		if data, err := io.ReadAll(r.Body); err == nil {
			body = data
		}
	}
	if len(body) == 0 {
		zap.L().Error("Invalid request, empty body")
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	contentType := r.Header.Get("Content-Type")
	if contentType != "application/json" {
		zap.L().Error("Invalid request, invalid content-type", zap.Any("content_type", contentType))
		http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)
		return
	}

	// 构造response
	var admissionResponse *admissionv1.AdmissionResponse

	// 反序列化请求, 将结果反序列化到ar中
	ar := admissionv1.AdmissionReview{}
	if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
		zap.L().Error("deserializer decode failed", zap.Error(err), zap.Any("body", body))
		admissionResponse = &admissionv1.AdmissionResponse{
			Result: &metav1.Status{
				Message: err.Error(),
			},
		}
	} else {
		// 反序列化成功, 就根据路径调用不同的webhook
		if r.URL.Path == "/mutate" {
			admissionResponse = whsvr.mutate(&ar)
		} else if r.URL.Path == "/validate" {
			admissionResponse = whsvr.validate(&ar)
		}
	}

	// 构造返回给ApiServer的 admissionReview
	admissionReview := admissionv1.AdmissionReview{}
	admissionReview.TypeMeta = metav1.TypeMeta{
		Kind:       "AdmissionReview",
		APIVersion: "admission.k8s.io/v1",
	}
	if admissionResponse != nil {
		// 填入wehbook函数返回的admissionResponse
		admissionReview.Response = admissionResponse
		if ar.Request != nil {
			// 填入请求时的UID
			admissionReview.Response.UID = ar.Request.UID
		}
	}

	// 序列化admissionReview, 结果写入resp
	resp, err := json.Marshal(admissionReview)
	if err != nil {
		zap.L().Error("Marshal response failed", zap.Error(err), zap.Any("admission_review", admissionReview))
		http.Error(w, "Marshal response failed", http.StatusInternalServerError)
	}

	// 返回请求, 将序列化结构resp写入http.ResponseWriter
	if _, err := w.Write(resp); err != nil {
		zap.L().Error("Write response failed", zap.Error(err), zap.String("response", string(resp)))
		http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
	}
	zap.L().Info("Admission review response", zap.String("response", string(resp)))
}
